// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 1992, 1998-2006 Linus Torvalds, Ingo Molnar
 * Copyright (C) 2005-2006, Thomas Gleixner, Russell King
 *
 * This file contains the core interrupt handling code. Detailed
 * information is available in Documentation/core-api/genericirq.rst
 *
 */
#include <seminix/errno.h>
#include <seminix/cache.h>
#include <seminix/init.h>
#include <seminix/jiffies.h>
#include <seminix/dump_stack.h>
#include <seminix/irq.h>
#include <seminix/irqdesc.h>
#include <seminix/irq/chip.h>
#include <seminix/irq/handle.h>
#include <asm/ptrace.h>
#include "internal.h"

void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;

int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
    if (handle_arch_irq)
        return -EBUSY;

    handle_arch_irq = handle_irq;
    return 0;
}

/**
 * handle_bad_irq - handle spurious and unhandled irqs
 * @desc:      description of the interrupt
 *
 * Handles spurious and unhandled IRQ's. It also prints a debugmessage.
 */
void handle_bad_irq(struct irq_desc *desc)
{
    int irq = irq_desc_get_irq(desc);

    print_irq_desc(irq, desc);
    ack_bad_irq(irq);
}

/*
 * Special, empty irq handler:
 */
irqreturn_t no_action(int cpl, void *dev_id)
{
    return IRQ_NONE;
}

__diag_push()
__diag_ignore_all("-Wimplicit-fallthrough", "");

irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
    irqreturn_t retval = IRQ_NONE;
    int irq = desc->irq_data.irq;
    struct irqaction *action;

    for_each_action_of_desc(desc, action) {
        irqreturn_t res;

        res = action->handler(irq, action->dev_id);

        if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
                  irq, action->handler))
            local_irq_disable();

        switch (res) {
        /* Fall through to add to randomness */
        default:
            break;
        case IRQ_IPC:
            __irqd_set(&desc->irq_data, IRQ_INIPC);
        case IRQ_HANDLED:
            *flags |= action->flags;
            break;
        }

        retval |= res;
    }

    return retval;
}

__diag_pop();

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
    irqreturn_t retval;
    unsigned int flags = 0;

    retval = __handle_irq_event_percpu(desc, &flags);

    note_interrupt(desc, retval);

    return retval;
}

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    irqreturn_t ret;

    __irqd_clear(&desc->irq_data, IRQ_PENDING);
    __irqd_set(&desc->irq_data, IRQ_INPROGRESS);
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc);

    raw_spin_lock(&desc->lock);
    __irqd_clear(&desc->irq_data, IRQ_INPROGRESS);
    return ret;
}

static inline int bad_action_ret(irqreturn_t action_ret)
{
    unsigned int r = action_ret;

    if (likely(r <= (IRQ_HANDLED)))
        return 0;
    return 1;
}

/*
 * If 99,900 of the previous 100,000 interrupts have not been handled
 * then assume that the IRQ is stuck in some manner. Drop a diagnostic
 * and try to turn the IRQ off.
 *
 * (The other 100-of-100,000 interrupts may have been a correctly
 *  functioning device sharing an IRQ with the failing one)
 */
static void __report_bad_irq(struct irq_desc *desc, irqreturn_t action_ret)
{
    int irq = irq_desc_get_irq(desc);
    struct irqaction *action;
    unsigned long flags;

    if (bad_action_ret(action_ret)) {
        pr_err("irq event %d: bogus return value %x\n",
                irq, action_ret);
    } else {
        pr_err("irq %d: nobody cared (try booting with "
                "the \"irqpoll\" option)\n", irq);
    }
    dump_stack();
    pr_err("handlers:\n");

    /*
     * We need to take desc->lock here. note_interrupt() is called
     * w/o desc->lock held, but IRQ_PROGRESS set. We might race
     * with something else removing an action. It's ok to take
     * desc->lock here. See synchronize_irq().
     */
    raw_spin_lock_irqsave(&desc->lock, flags);
    for_each_action_of_desc(desc, action) {
        pr_err("[<%p>] %pf", action->handler, action->handler);
        pr_cont("\n");
    }
    raw_spin_unlock_irqrestore(&desc->lock, flags);
}

static void report_bad_irq(struct irq_desc *desc, irqreturn_t action_ret)
{
    static int count = 100;

    if (count > 0) {
        count--;
        __report_bad_irq(desc, action_ret);
    }
}

void note_interrupt(struct irq_desc *desc, irqreturn_t action_ret)
{
    int irq;

    if (bad_action_ret(action_ret)) {
        report_bad_irq(desc, action_ret);
        return;
    }

    if (unlikely(action_ret == IRQ_NONE)) {
        /*
         * If we are seeing only the odd spurious IRQ caused by
         * bus asynchronicity then don't eventually trigger an error,
         * otherwise the counter becomes a doomsday timer for otherwise
         * working systems
         */
        if (time_after((unsigned long)jiffies_64, (unsigned long)desc->last_unhandled + HZ/10))
            desc->irqs_unhandled = 1;
        else
            desc->irqs_unhandled++;
        desc->last_unhandled = jiffies_64;
    }

    irq = irq_desc_get_irq(desc);

    desc->irq_count++;
    if (likely(desc->irq_count < 100000))
        return;

    desc->irq_count = 0;
    if (unlikely(desc->irqs_unhandled > 99900)) {
        /*
         * The interrupt is stuck
         */
        __report_bad_irq(desc, action_ret);
        /*
         * Now kill the IRQ
         */
        pr_emerg("Disabling IRQ #%d\n", irq);
        __irqd_set(&desc->irq_data, IRQ_MASKED);
        desc->depth++;
        mask_irq(desc);
    }
    desc->irqs_unhandled = 0;
}
